﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Chess.Common;

namespace Chess.Client
{
    using System.Windows.Threading;

    public class ClientService
    {
        private TcpClient _tcpClient;

        public event UpdateAction Update;

        public void OnUpdate(PackageType packagetype, params object[] args)
        {
            UpdateAction handler = Update; 
            if (handler != null)
            {
                dispatcher_.Invoke(() => handler(packagetype, args));
            }
        }

        private readonly Dispatcher dispatcher_;
        public ClientService(Dispatcher dispatcher)
        {
            dispatcher_ = dispatcher;
            //_userName = userName;
            //_password = password;
            // _manualResetEvent = token.WaitHandle as ManualResetEvent;
            // _manualResetEvent.Reset();
        }

        
        public async Task Connect(IPAddress address, int port)
        {
            if (_tcpClient != null && _tcpClient.Connected)
            {
                return;
            }

            _tcpClient = new TcpClient();
            _tcpClient.Connect(address, port);

            Task.Run( ()=> StartListeningAsync());
        }

        public bool Connected { get { return _tcpClient.Connected; } }

        private event Action<Package> Received;

        private async Task StartListeningAsync()
        {
            while (true)
            {
                try
                {
                    Package packageFromServer = await ReceivePackageAsync();
                    if (Received != null) 
                        Received(packageFromServer);

                    switch (packageFromServer.PackageType)
                    {
                        case PackageType.ClientsList:
                            OnUpdate(PackageType.ClientsList, packageFromServer);
                            break;
                        case PackageType.SuggestGame:
                            var request = packageFromServer as SuggestGamePackage;
                            var answerType = SuggestGameAnswerType.No;
                            if (MessageBox.Show(string.Format("Do you want start game with {0}?", request.UserName), "Question", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                            {
                                answerType = SuggestGameAnswerType.Ok;
                                OnUpdate(PackageType.SuggestGame, packageFromServer);
                            }
                            var answer = new SuggestGameAnswerPackage(userName_, request.UserName, answerType);
                            SendPackageAsync(answer).Wait();
                            break;
                        case PackageType.SuggestGameAnswer:
                            OnUpdate(PackageType.SuggestGameAnswer, packageFromServer);
                            break;
                            //case PackageType.SuggestGameAnswer:
                            //    MessageBox.Show("Suggest approved.");
                            //    break;
                        case PackageType.ChessMove:
                            OnUpdate(PackageType.ChessMove, packageFromServer);
                            break;

                    }
                }
                catch (SocketException e)
                {
                    _tcpClient.Close();
                    break;
                }
                catch (IOException)
                {
                    break;
                }
                catch (InvalidOperationException)
                {
                    break;
                }
                catch (Exception e) // TODO:
                {
                    MessageBox.Show(e.Message);
                }
            }
        }

        public async Task<RegistrationAnswerPackage> Register(string userName, string password)
        {
            var passwordHash = GetHash(password);
            var registerPackage = new RegistrationRequestPackage(userName, passwordHash);
            return await SendAndRecieve<RegistrationAnswerPackage>(registerPackage);
        }

        private async Task<T> SendAndRecieve<T>(Package toSend) where T : Package
        {
            T t = null;
            var e = new ManualResetEvent(false);

            Action<Package> recieve = p =>
            {
                if (p is T)
                {
                    t = (T)p;
                    e.Set();
                    
                }
            };

            Received += recieve;

            try
            {
                await SendPackageAsync(toSend);
                e.WaitOne();
            }
            finally
            {
                Received -= recieve;                
            }

            return t;
        }

        private string userName_;
        public async Task<LoginAnswerPackage> Login(string userName, string password)
        {
            var passwordHash = GetHash(password);
            var loginPackage = new LoginRequestPackage(userName, passwordHash);
            userName_ = userName;
            return await SendAndRecieve<LoginAnswerPackage>(loginPackage);
        }

        public async Task<SuggestGameAnswerPackage> SuggestGame(string toUser)
        {
            var suggestGamePackage = new SuggestGamePackage(userName_, toUser);
            return await SendAndRecieve<SuggestGameAnswerPackage>(suggestGamePackage);
        }

        public async Task MovePiece(string toUser, BytePoint from, BytePoint to)
        {
            var suggestGamePackage = new ChessMovePackage(userName_, toUser, from, to);
            await SendPackageAsync(suggestGamePackage);
        }

        public async Task Logout()
        {
            var logout = new LogoutRequestPackage(userName_);
            await SendPackageAsync(logout);
        }

        public async Task Exit()
        {
            if (_tcpClient.Connected)
            {
                var exitPackage = new ExitPackage(userName_);
                await SendPackageAsync(exitPackage);
            }
        }

        public Task SendPackageAsync(Package package)
        {
            var buffer = PackageSerializer.Serialize(package);
            //using (var ns = _tcpClient.GetStream())
            var ns = _tcpClient.GetStream();
            {
                return ns.WriteAsync(buffer, 0, buffer.Length);
                //ns.Write(buffer, 0, buffer.Length); // TODO: write async
            }
        }

        public async Task<Package> ReceivePackageAsync()
        {                    
            var stream = _tcpClient.GetStream();
            byte[] header = new byte[4];
            stream.Read(header, 0, sizeof(int));

            var packageLength = BitConverter.ToInt32(header, 0);
            byte[] buffer = new byte[packageLength];
                        
            var task = stream.ReadAsync(buffer, 0, buffer.Length);
            await task;

            return PackageSerializer.Deserialize(buffer);
        }

        private string GetHash(string password)
        {
            SHA1 sha = new SHA1CryptoServiceProvider();
            var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(password));
            string hash = Encoding.UTF8.GetString(bytes);
            return hash;
        }
    }
}
